import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:homepad/iot/basic_types.dart';
import 'package:homepad/iot/message_def.dart';
import 'package:homepad/logger/logger.dart';
import 'package:web_socket_channel/status.dart' as status;
import 'package:web_socket_channel/web_socket_channel.dart';

enum WebsocketState {
  init,
  connecting,
  established,
  closed,
  destroy;
}

abstract interface class WebsocketDelegate {
  void onWebsocketStateChanged(WebsocketState state);
  void onAttributeReport(AttributeDataIB report);
}

class WebsocketClient {
  late Uri wsUrl;
  WebSocketChannel? client;
  bool _started = false;

  late WebsocketDelegate _delegate;
  set delegate(WebsocketDelegate delegate) {
    _delegate = delegate;
  }

  Future<void> dialAndTransfer() async {
    final c = WebSocketChannel.connect(wsUrl);
    try {
      _delegate.onWebsocketStateChanged(WebsocketState.connecting);
      await c.ready;
      client = c;
      _delegate.onWebsocketStateChanged(WebsocketState.established);
      await for (final data in c.stream) {
        logger.info("ws <<--  $data");
        final map = jsonDecode(data as String) as Map<String, dynamic>;
        final message = Message.fromJson(map);
        _handleMessage(message);
      }
      client = null;
      _delegate.onWebsocketStateChanged(WebsocketState.closed);
      logger.info("closeCode: ${c.closeCode} closeReason: ${c.closeReason}");
    } on SocketException catch (e) {
      logger.warning(e.toString());
    } on WebSocketChannelException catch (e) {
      logger.warning(e.toString());
    } catch (e) {
      logger.warning(e.toString());
    }
  }

  void start(String remote) {
    if (_started) {
      return;
    }
    _started = true;
    wsUrl = Uri.parse(remote);
    Future.delayed(const Duration(seconds: 1), () async {
      while (true) {
        logger.info("dialAndTransfer remote: $remote");
        await dialAndTransfer();

        final completer = Completer();
        Future.delayed(const Duration(seconds: 3), () {
          completer.complete();
        });
        await completer.future;
      }
    });
  }

  void sendMessage(Message message) {
    var data = jsonEncode(message.toJson());
    logger.info("ws -->> $data");
    client?.sink.add(data);
  }

  void writeRequest(AttributeDataIB attribute) {
    var req = Message(method: MethodType.writeRequest, data: attribute);
    sendMessage(req);
  }

  void readRequest(AttributePathIB path) {
    var req = Message(method: MethodType.readRequest, data: path);
    sendMessage(req);
  }

  void _handleMessage(Message message) {
    switch (message.method) {
      case MethodType.reportAttribute:
        final report = AttributeDataIB.fromJson(message.data);
        _delegate.onAttributeReport(report);

      case MethodType.readResponse:
        for (var item in (message.data as List<dynamic>)) {
          final data = AttributeDataIB.fromJson(item as Map<String, dynamic> );
          if (data.status != StatusCode.success) {
            logger.error("error_code: ${data.status?.name}");
            continue;
          }
          _delegate.onAttributeReport(data);
        }
      default:
        break;
    }
  }

  void close() {
    client?.sink.close(status.normalClosure);
  }
}
